#include "logger.h"

#include <filesystem> 
#include <map>
#include <sstream>
#include <stdarg.h>
#include <stdexcept>
#include <string.h>
#include <sys/time.h>
#include <time.h>

namespace Base {
namespace SystemLog {
namespace {
const char *logLevelNames[LOG_LEVEL_COUNT] = {
    "LOG_LEVEL_DEBUG",
    "LOG_LEVEL_INFO",
    "LOG_LEVEL_WARN",
    "LOG_LEVEL_ERROR",
    "LOG_LEVEL_FATAL"
};

const std::map<LogLevel, std::string> colors = {
    {LOG_LEVEL_DEBUG,   "\033[32m"}, // 绿色
    {LOG_LEVEL_INFO,    "\033[37m"}, // 白色
    {LOG_LEVEL_WARN, "\033[33m"}, // 黄色
    {LOG_LEVEL_ERROR,   "\033[31m"}, // 红色
    {LOG_LEVEL_FATAL,   "\033[1;41m"} // 白字红底
};
} // namespace
Logger::Logger()
{
    Open(GenerateLogFileName());
}

Logger::~Logger()
{
    Close();
}

void Logger::Open(const std::string &fileName)
{
    std::filesystem::path filePath(fileName);
    std::filesystem::path dirPath = filePath.parent_path();
    
    if (!dirPath.empty() && !std::filesystem::exists(dirPath)) {
        std::error_code ec;
        if (!std::filesystem::create_directories(dirPath, ec)) {
            throw std::runtime_error("create log directory failed: " + dirPath.string() + " - " + ec.message());
        }
    }

    outFile_.open(fileName, std::ios::out | std::ios::app);
    if (!outFile_) {
        throw std::runtime_error("open log file failed: " + fileName);
    }
    outFile_.seekp(0, std::ios::end);
    currentFileSize_ = static_cast<int32_t>(outFile_.tellp());
    if (currentFileSize_ < 0) {
        throw std::runtime_error("get current file size failed: " + fileName);
    }
    fileName_ = fileName;
}

void Logger::Close()
{
    if (outFile_.is_open()) {
        outFile_.close();
    }
}

std::string Logger::GenerateLogPrefix(LogLevel level, const char* file, int line) const
{
    timeval tv;
    gettimeofday(&tv, nullptr);
    tm* timeInfo = localtime(&tv.tv_sec);
    if (timeInfo == nullptr) {
        throw std::runtime_error("get local time failed");
    }

    char timeStamp[64];
    strftime(timeStamp, sizeof(timeStamp), "%Y-%m-%d %H:%M:%S", timeInfo);
    
    char fullTimeStamp[80];
    snprintf(fullTimeStamp, sizeof(fullTimeStamp), "%s.%03d", 
             timeStamp, static_cast<int>(tv.tv_usec / 1000));

    const char* fileNameOnly = strrchr(file, '/');
    fileNameOnly = fileNameOnly ? fileNameOnly + 1 : file;

    std::ostringstream oss;
    oss << colors.at(level) << "[" << fullTimeStamp << "] " << colors.at(level) << logLevelNames[level]
        << " [" << fileNameOnly << ":" << line << "]" << " ";
    return oss.str();
}

void Logger::Log(LogLevel level, const char* file, int line, const char* format, ...)
{
    if (level < logLevel_) {
        return;
    }
    if (!outFile_.is_open()) {
        throw std::runtime_error("open log file failed: " + fileName_);
    }

    std::string prefix = GenerateLogPrefix(level, file, line);
    outFile_ << prefix;
    std::cout << prefix ;

    va_list args;
    va_start(args, format);
    char* message = nullptr;
    int len = vasprintf(&message, format, args);
    va_end(args);
    if (len <= 0) {
        throw std::runtime_error("format log message failed");
    }
    outFile_ << message << "\n";
    std::cout << message << "\033[0m" << "\n";
    free(message);
    currentFileSize_ += static_cast<int32_t>(prefix.size()) + len + 1;

    outFile_.flush();
    if (maxFileSize_ > 0 && currentFileSize_ >= maxFileSize_) {
        RotateLogFile();
    }
}

std::string Logger::GenerateLogFileName() const
{
    time_t now = time(nullptr);
    struct tm *timeInfo = localtime(&now);
    char timeStamp[64];
    strftime(timeStamp, sizeof(timeStamp), "logs/%Y-%m-%d-%H:%M:%S.log", timeInfo);
    return std::string(timeStamp);
}

void Logger::RotateLogFile()
{
    Close();
    currentFileSize_ = 0;
    Open(GenerateLogFileName());
}
} // namespace SystemLog
} // namespace Base
